package com.sola.oauthserver.config2;




import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;


@Configuration
public class OAuth2ServerConfig {
    private static final String DEMO_RESOURCE_ID = "order";


/**
 * 认证服务器同时也管理着user信息资源，所以也是资源服务器。
 * */
    @Order(1)
    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {


        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            //resourceId 用于分配给可授予的clientId
            //stateless  标记以指示在这些资源上仅允许基于令牌的身份验证
            //tokenStore token的存储方式（上一章节提到）
            resources
                    .resourceId(DEMO_RESOURCE_ID).stateless(true);
        }

        //设置具体的访问拦截规则。
        @Override
        public void configure(HttpSecurity http) throws Exception {

            http.csrf()
                    // 我们希望在UI中也能访问受保护的资源
                    //                    // 允许创建session， (2.X中默认是禁止)
                    //                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    //                    .and()
                    .disable()
                    .authorizeRequests()
                    //这里的拦截规则会覆盖security的设置，而且至少声明一个放行的路径，这样拦截才能生效，否者报错
                    .antMatchers("/login")
                    .permitAll()
                    .anyRequest()
                    .authenticated();

        }
    }


    //默认order=3
    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private TokenStore tokenStore;
        @Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;

        @Autowired
        private TokenEnhancer jwtTokenEnhancer;

        @Autowired
        AuthenticationManager authenticationManager;
        @Autowired
        RedisConnectionFactory redisConnectionFactory;
        @Resource
        DataSource dataSource;

        //创建一个加密的bean
        @Bean
        public PasswordEncoder bCryptPasswordEncoder() {
            return new BCryptPasswordEncoder();
        }

        /*@Bean
        public ClientDetailsService clientDetails() {
            return new JdbcClientDetailsService(dataSource);
        }*/

      /**
       * 使用内存方式这个bean要注释掉
       * 使用数据库，需要这个bean
       *
       * */
      @Bean
      public PasswordEncoder passwordEncoder(){return NoOpPasswordEncoder.getInstance();
      }
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
         /**
           * 内存方式
            *
            * //配置两个客户端,一个用于password认证一个用于client认证*/
           /* clients.inMemory().withClient("webApp")
                    //授予client_1  一个或者多个resourceId，该resourceId可以关联到ResourceServerConfiguration中声明的一些设置
                    .resourceIds(DEMO_RESOURCE_ID)
                    //声明使用的认证方式：授权码模式，刷新token，password模式
                    .authorizedGrantTypes("authorization_code", "client_credentials", "refresh_token",
                            "password", "implicit")
                    //权限范围
                    .scopes("select")
                    //设置回调地址
                    .redirectUris("http://www.baidu.com")
                    //
                    .authorities("client")
                    //secret相当于密码，spring boot 2.X以后必须进行加密
                    .secret(bCryptPasswordEncoder().encode("123456"))
                    .and()
                    .withClient("client_2")
                    .resourceIds(DEMO_RESOURCE_ID)
                    //声明使用password认证
                    .authorizedGrantTypes("authorization_code", "refresh_token","password")
                    .scopes("select")
                    .authorities("client")
                    //secret相当于密码，spring boot 2.X以后必须进行加密
                    .secret(bCryptPasswordEncoder().encode("123456"))
                    //token过期时间   未起效
                    .accessTokenValiditySeconds(3600)
                    //refresh token过期时间   未起效
                    .refreshTokenValiditySeconds(6000);*/
            /**
            * 数据库方式
            * */
           clients.jdbc(dataSource);


        }


        /*@Bean
        public TokenStore tokenStore(){
            return new RedisTokenStore(redisConnectionFactory);
        }*/
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                    //声明使用JWT  token
                    .tokenStore(tokenStore)
                    //声明一个转换器
                    .accessTokenConverter(jwtAccessTokenConverter)
                    //使用redis存放token
                    //.tokenStore(tokenStore())
                    //refresh_token 需要 UserDetailsService is required
                    // .userDetailsService(userDetailsService)
                    //声明认证管理器
                    .authenticationManager(authenticationManager);

            //要让JWT增强器生效，需要下列配置
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> enhancerList = new ArrayList<>();
            enhancerList.add(jwtTokenEnhancer);
            enhancerList.add(jwtAccessTokenConverter);
            enhancerChain.setTokenEnhancers(enhancerList);
            endpoints
                    .tokenEnhancer(enhancerChain);
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
                 //允许表单认证;
            oauthServer
                    .allowFormAuthenticationForClients()
                    .tokenKeyAccess("permitAll()")
                    .checkTokenAccess("isAuthenticated()");

        }

    }

}
